﻿from datetime import datetime, timezone, timedelta, date as date_type

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import or_, select, func
import sqlalchemy as sa
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db
from app.models.cycle import CycleLog, CyclePrediction
from sqlalchemy import insert
from sqlalchemy.dialects.mysql import insert as mysql_insert
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
from app.models.user import User
from app.schemas import cycle as cycle_schema
from app.api.response import success_response

router = APIRouter(redirect_slashes=False)

PHASE_LABELS = {
  'menstruation': '月经期',
  'follicular': '卵泡期',
  'fertile': '排卵期',
  'luteal': '黄体期',
}


def _log_accessible(log: CycleLog | None, user: User) -> bool:
  if not log:
    return False
  if log.user_id == user.id:
    return True
  if user.active_relationship_id and log.relationship_id == user.active_relationship_id:
    return True
  return False


def _prediction_filter(user: User):
  if user.active_relationship_id:
    return or_(
      CyclePrediction.user_id == user.id,
      CyclePrediction.relationship_id == user.active_relationship_id,
    )
  return CyclePrediction.user_id == user.id


@router.get('')
async def list_cycle_logs(
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  filters = []
  if current_user.active_relationship_id:
    filters.append(
      or_(
        CycleLog.user_id == current_user.id,
        CycleLog.relationship_id == current_user.active_relationship_id,
      )
    )
  else:
    filters.append(CycleLog.user_id == current_user.id)

  result = await session.scalars(select(CycleLog).where(*filters))
  logs = result.all()
  payload = [
    cycle_schema.CycleLogResponse.model_validate(log, from_attributes=True).model_dump(mode='json')
    for log in logs
  ]
  return success_response(payload)


@router.post('', status_code=status.HTTP_201_CREATED)
async def create_cycle_log(
  payload: cycle_schema.CycleLogCreate,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  log = CycleLog(
    user_id=current_user.id,
    relationship_id=current_user.active_relationship_id,
    start_date=payload.start_date,
    end_date=payload.end_date,
    symptoms_json=payload.symptoms,
    notes=payload.notes,
  )
  session.add(log)
  await session.commit()
  await session.refresh(log)
  response = cycle_schema.CycleLogResponse.model_validate(log, from_attributes=True)
  return success_response(response.model_dump(mode='json'))


@router.put('/{log_id}')
async def update_cycle_log(
  log_id: int,
  payload: cycle_schema.CycleLogUpdate,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  log = await session.get(CycleLog, log_id)
  if not _log_accessible(log, current_user):
    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Cycle log not found')

  for field, value in payload.model_dump(exclude_unset=True).items():
    if field == 'symptoms':
      log.symptoms_json = value
    else:
      setattr(log, field, value)

  session.add(log)
  await session.commit()
  await session.refresh(log)
  response = cycle_schema.CycleLogResponse.model_validate(log, from_attributes=True)
  return success_response(response.model_dump(mode='json'))


@router.delete('/{log_id}')
async def delete_cycle_log(
  log_id: int,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  log = await session.get(CycleLog, log_id)
  if not _log_accessible(log, current_user):
    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Cycle log not found')
  log.deleted_at = datetime.now(tz=timezone.utc)
  session.add(log)
  await session.commit()
  return success_response()


def _calculate_cycle_predictions(
  *,
  flow_days: list[tuple[date_type, str]],
  fallback_date: date_type,
  relationship_id: int | None,
  user_id: int,
) -> list[dict]:
  if not flow_days:
    return []

  flow_days = sorted(flow_days, key=lambda item: item[0])
  flow_dates = [date for date, level in flow_days if level and level.lower() != 'none']
  if not flow_dates:
    return []

  flow_set = set(flow_dates)
  cycle_starts: list[date_type] = []
  period_lengths: list[int] = []
  prev_date: date_type | None = None

  for current_date in flow_dates:
    if prev_date is None or (current_date - prev_date).days > 1:
      cycle_starts.append(current_date)
      length = 1
      probe = current_date + timedelta(days=1)
      while probe in flow_set:
        length += 1
        probe += timedelta(days=1)
      period_lengths.append(length)
    prev_date = current_date

  if len(cycle_starts) < 1:
    return []

  cycle_lengths = [
    (cycle_starts[idx + 1] - cycle_starts[idx]).days
    for idx in range(len(cycle_starts) - 1)
    if (cycle_starts[idx + 1] - cycle_starts[idx]).days > 0
  ]

  if cycle_lengths:
    avg_cycle = max(20, round(sum(cycle_lengths) / len(cycle_lengths)))
  else:
    avg_cycle = 28

  avg_period = max(3, round(sum(period_lengths) / len(period_lengths))) if period_lengths else 5

  last_start = cycle_starts[-1]
  next_period_start = max(fallback_date, last_start + timedelta(days=avg_cycle))
  menstruation_end = next_period_start + timedelta(days=avg_period - 1)

  fertile_peak = next_period_start - timedelta(days=14)
  fertile_start = fertile_peak - timedelta(days=2)
  fertile_end = fertile_peak + timedelta(days=2)

  follicular_start = menstruation_end + timedelta(days=1)
  follicular_end = fertile_start - timedelta(days=1)

  luteal_start = fertile_end + timedelta(days=1)
  luteal_end = next_period_start - timedelta(days=1)

  history_score = min(len(cycle_lengths), 6) / 6 if cycle_lengths else 0.0

  consistency_score = 0.0
  if cycle_lengths:
    avg_cycle_length = sum(cycle_lengths) / len(cycle_lengths)
    deviation = sum(abs(length - avg_cycle_length) for length in cycle_lengths) / len(cycle_lengths)
    # deviation < 6 表示比较稳定，大于 12 认为完全不稳定
    spread_ratio = min(max(deviation - 2, 0) / 10, 1.0)
    consistency_score = 1.0 - spread_ratio

  coverage_score = 0.0
  if cycle_starts:
    theoretical_days = max(avg_period * len(cycle_starts), 1)
    coverage_score = min(len(flow_dates) / theoretical_days, 1.0)

  confidence = 0.3 + 0.45 * history_score + 0.25 * consistency_score + 0.1 * coverage_score
  confidence = min(confidence, 0.95)
  confidence = round(confidence, 2)

  def build(phase: str, start: date_type, end: date_type) -> dict | None:
    if start > end:
      return None
    label = PHASE_LABELS.get(phase, phase)
    return {
      'id': hash((phase, start)) & 0xFFFFFFFF,
      'phase': phase,
      'phase_label': label,
      'start_date': start.isoformat(),
      'end_date': end.isoformat(),
      'confidence': confidence,
      'relationship_id': relationship_id,
      'user_id': user_id,
    }

  predictions: list[dict] = []
  period = build('menstruation', next_period_start, menstruation_end)
  if period:
    predictions.append(period)
  follicular = build('follicular', follicular_start, follicular_end)
  if follicular:
    predictions.append(follicular)
  fertile = build('fertile', fertile_start, fertile_end)
  if fertile:
    predictions.append(fertile)
  luteal = build('luteal', luteal_start, luteal_end)
  if luteal:
    predictions.append(luteal)
  return predictions


@router.get('/predictions')
async def list_cycle_predictions(
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  filters = [_prediction_filter(current_user)]
  result = await session.scalars(select(CyclePrediction).where(*filters).order_by(CyclePrediction.start_date))
  persisted = result.all()
  if persisted:
    payload = [
      cycle_schema.CyclePrediction.model_validate(pred, from_attributes=True).model_dump(mode='json')
      for pred in persisted
    ]
    for item in payload:
      item['phase_label'] = PHASE_LABELS.get(item.get('phase'), item.get('phase'))
    return success_response(payload)

  rid = current_user.active_relationship_id
  day_logs_stmt = sa.text(
    """
    SELECT date, flow_level FROM cycle_day_logs
    WHERE deleted_at IS NULL AND (user_id=:uid OR (:rid IS NOT NULL AND relationship_id=:rid))
    ORDER BY date ASC
    """
  )
  rows = await session.execute(day_logs_stmt, {'uid': current_user.id, 'rid': rid})
  flow_days = []
  for row in rows:
    day = row[0]
    if isinstance(day, str):
      day = datetime.strptime(day, '%Y-%m-%d').date()
    flow_days.append((day, row[1]))

  today = datetime.now(timezone.utc).date()
  predictions = _calculate_cycle_predictions(
    flow_days=flow_days,
    fallback_date=today,
    relationship_id=rid,
    user_id=current_user.id,
  )
  return success_response(predictions)


@router.get('/day-logs')
async def list_cycle_day_logs(
  month: int, year: int,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  start = datetime(year, month, 1)
  if month == 12:
    end = datetime(year + 1, 1, 1)
  else:
    end = datetime(year, month + 1, 1)
  # raw SQL to avoid model import for brevity
  rows = await session.execute(
    sa.text(
      "SELECT * FROM cycle_day_logs WHERE user_id=:uid AND date >= :start AND date < :end AND deleted_at IS NULL ORDER BY date"
    ),
    {"uid": current_user.id, "start": start.date(), "end": end.date()},
  )
  data = [dict(r) for r in rows.mappings()]
  return success_response(data)


@router.post('/day-logs')
async def upsert_cycle_day_log(
  payload: cycle_schema.CycleDayLogCreate,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  values = {
    'user_id': current_user.id,
    'relationship_id': current_user.active_relationship_id,
    'date': payload.date,
    'flow_level': payload.flow_level,
    'symptoms_json': payload.symptoms,
    'mood': payload.mood,
    'temperature': payload.temperature,
  }
  # use generic upsert
  stmt = sa.text(
    """
    INSERT INTO cycle_day_logs (user_id, relationship_id, date, flow_level, symptoms_json, mood, temperature)
    VALUES (:user_id, :relationship_id, :date, :flow_level, :symptoms_json, :mood, :temperature)
    ON DUPLICATE KEY UPDATE flow_level=VALUES(flow_level), symptoms_json=VALUES(symptoms_json), mood=VALUES(mood), temperature=VALUES(temperature), deleted_at=NULL
    """
  )
  try:
    await session.execute(stmt, values)
  except Exception:
    # fallback for SQLite (replace semantics)
    await session.execute(
      sa.text(
        "DELETE FROM cycle_day_logs WHERE user_id=:user_id AND date=:date"
      ),
      values,
    )
    await session.execute(
      sa.text(
        "INSERT INTO cycle_day_logs (user_id, relationship_id, date, flow_level, symptoms_json, mood, temperature) VALUES (:user_id, :relationship_id, :date, :flow_level, :symptoms_json, :mood, :temperature)"
      ),
      values,
    )
  await session.commit()
  return success_response({'ok': True})


from fastapi import Query
from datetime import date as date_type


@router.delete('/day-logs')
async def delete_cycle_day_log(
  date: date_type = Query(..., description='日期，格式 YYYY-MM-DD'),
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:

  stmt = sa.text(
    "UPDATE cycle_day_logs SET deleted_at = :deleted_at WHERE user_id=:uid AND date=:date"
  )
  await session.execute(
    stmt,
    {
      'deleted_at': datetime.now(timezone.utc),
      'uid': current_user.id,
      'date': date,
    },
  )
  await session.commit()
  return success_response({'ok': True})
